/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/stream/storage_file_interface.h>

namespace baja { namespace storage {

// Bug 451 : TRACK: Gmileka: need to switch storage_file back to using ppl tasks again

class storage_file :
    public istorage_file,
    public std::enable_shared_from_this<istorage_file>
{
public:
    storage_file()
    {
    }

    static std::shared_ptr<istorage_file> create()
    {
        std::shared_ptr<storage_file> returnValue = std::make_shared<storage_file>();
        return returnValue;
    }

    static std::shared_ptr<istorage_file> create(Windows::Storage::StorageFile^ storageFile)
    {
        std::shared_ptr<storage_file> returnValue = std::make_shared<storage_file>();
        returnValue->_storageFile = storageFile;
        return returnValue;
    }

    // istorage_file
    void initializeAsync(std::function<void(bool)> initializeCompleted)
    {
        if (_storageFile == nullptr)
        {
            BAJA_THROW(std::runtime_error("object is not initialized"));
        }
        initializePropertiesAsync(initializeCompleted);
    }

    void onStorageFileReady( Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ op, std::function<void(bool)> initializeCompleted)
    {
        std::shared_ptr<storage_file> p_this = std::static_pointer_cast<storage_file>(shared_from_this());

        op->Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>(
                [p_this, initializeCompleted](Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ operation, AsyncStatus status)
                {
                    bool success = true;
                    try
                    {
                        p_this->_storageFile = operation->GetResults();
                        p_this->initializePropertiesAsync(initializeCompleted);
                    }
                    catch(Platform::Exception^ e)
                    {
                        success = false;
                    }
                    if (p_this->_storageFile == nullptr)
                    {
                        success = false;
                        initializeCompleted(success);
                    }
                }
            );
    }

    void initializeAsync(std::wstring fullFileName, std::function<void(bool)> initializeCompleted)
    {
        Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ op = Windows::Storage::StorageFile::GetFileFromPathAsync(ref new Platform::String(fullFileName.c_str()));

        onStorageFileReady(op, initializeCompleted);
    }

    void initializeAsync(
        const known_folders& knownFolders,
        std::wstring fileName,
        std::function<void(bool)> initializeCompleted)
    {
        Windows::Storage::IStorageFolder^ folder;
        switch (knownFolders)
        {
        case known_folders::picture_library:
            folder = Windows::Storage::KnownFolders::PicturesLibrary;
            break;
        default:
            BAJA_THROW(std::runtime_error("unsupported file source"));
        }

        Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ op = folder->GetFileAsync(ref new Platform::String(fileName.c_str()));

        onStorageFileReady(op, initializeCompleted);
    }

    void initializeFromPickerAsync(
        const known_folders& knownFolders,
        const std::vector<std::wstring>& fileTypeFilter,
        std::function<void(bool success, bool cancelled)> pickCompleted)
    {
        std::shared_ptr<storage_file> p_this = std::static_pointer_cast<storage_file>(shared_from_this());

        Windows::Storage::Pickers::FileOpenPicker^ fileOpenPicker = ref new Windows::Storage::Pickers::FileOpenPicker();

        // set the filter
        for (auto &iter : fileTypeFilter)
        {
            fileOpenPicker->FileTypeFilter->Append(ref new Platform::String(iter.c_str(), static_cast<b::uint32>(iter.size())));
        }

        // pick the folder
        switch (knownFolders)
        {
        case known_folders::picture_library:
            fileOpenPicker->SuggestedStartLocation = Windows::Storage::Pickers::PickerLocationId::PicturesLibrary;
            break;
        }

        // show the picker
        Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ op = fileOpenPicker->PickSingleFileAsync();

        op->Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>(
            [p_this, pickCompleted](Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ operation, AsyncStatus status)
        {
            bool success = true;
            bool cancelled = false;
            try
            {
                p_this->_storageFile = operation->GetResults();
                cancelled = p_this->_storageFile == nullptr;

                if (!cancelled)
                {
                    p_this->initializePropertiesAsync(
                            [pickCompleted](bool success)
                            {
                                pickCompleted(success, false);
                            }
                        );
                }

            }
            catch(Platform::Exception^ e)
            {
                success = false;
            }
            if (success == false || (success == true && cancelled == true))
            {
                pickCompleted(success, cancelled);
            }
        });
    }

    void deleteAsync(std::function<void(bool)> deleteCompleted)
    {
        if (_storageFile == nullptr)
        {
            BAJA_THROW(std::runtime_error("object is not initialized"));
        }

        Windows::Foundation::IAsyncAction^ ac = _storageFile->DeleteAsync();
        ac->Completed = ref new Windows::Foundation::AsyncActionCompletedHandler(
                [deleteCompleted](Windows::Foundation::IAsyncAction^ ac, AsyncStatus status)
                {
                    bool success = true;
                    try
                    {
                        ac->GetResults();
                    }
                    catch(Platform::Exception^ e)
                    {
                        success = false;
                    }
                    deleteCompleted(success);
                }
            );
    }

    std::wstring fileName()
    {
        if (_storageFile == nullptr)
        {
            BAJA_THROW(std::runtime_error("object is not initialized"));
        }

        return std::wstring(_storageFile->Name->Data(), _storageFile->Name->Length());
    }

    std::wstring fullFileName()
    {
        if (_storageFile == nullptr)
        {
            BAJA_THROW(std::runtime_error("object is not initialized"));
        }

        return std::wstring(_storageFile->Path->Data(), _storageFile->Path->Length());
    }

    b::uint64 modifiedDateTime()
    {
        if (_storageFileProperties == nullptr)
        {
            BAJA_THROW(std::runtime_error("object is not initialized"));
        }
        return _storageFileProperties->DateModified.UniversalTime;
    }

    void copyToAsync(std::wstring targetFullFileName, std::function<void(bool success, std::shared_ptr<istorage_file> copiedFile)> copyCompleted)
    {
        if (_storageFile == nullptr)
        {
            BAJA_THROW(std::runtime_error("object is not initialized"));
        }

        std::shared_ptr<storage_file> p_this = std::static_pointer_cast<storage_file>(shared_from_this());

        std::wstring temptargetFullFileName = targetFullFileName;
        std::wstring fileName;
        std::wstring pathName;

        std::wstring::size_type l = temptargetFullFileName.rfind(L'\\');
        if (l == std::wstring::npos)
        {
            fileName = temptargetFullFileName;
        }
        else
        {
            fileName = temptargetFullFileName.substr(l + 1);
            pathName = temptargetFullFileName.substr(0, l);
        }

        if (0 != wcscmp(Windows::Storage::ApplicationData::Current->LocalFolder->Path->Data(), pathName.c_str()))
        {
            BAJA_THROW(std::runtime_error("target folder location not supported!"));
        }

        Windows::Storage::StorageFolder^ folder = Windows::Storage::ApplicationData::Current->LocalFolder;

        Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ op = _storageFile->CopyAsync(folder, ref new Platform::String(fileName.c_str()));

        op->Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler<Windows::Storage::StorageFile^>(
                [p_this, copyCompleted](Windows::Foundation::IAsyncOperation<Windows::Storage::StorageFile^>^ operation, AsyncStatus status)
                {
                    bool success = true;
                    std::shared_ptr<istorage_file> copiedFile;
                    try
                    {
                        copiedFile = storage_file::create(operation->GetResults());
                    }
                    catch(Platform::Exception^ e)
                    {
                        success = false;
                    }
                    copyCompleted(success, copiedFile);
                }
            );
    }

    void createMemoryStreamFromFileAsync(std::function<void(bool, const std::shared_ptr<imemory_stream>& ms)> readCompletedFunc)
    {
        if (_storageFile == nullptr)
        {
            BAJA_THROW(std::runtime_error("object is not initialized"));
        }

        std::shared_ptr<storage_file> p_this = std::static_pointer_cast<storage_file>(shared_from_this());

        std::shared_ptr<Windows::Storage::Streams::DataReader^> context = std::make_shared<Windows::Storage::Streams::DataReader^>(nullptr);

        Windows::Foundation::IAsyncOperation<Windows::Storage::Streams::IRandomAccessStream^>^ op = _storageFile->OpenAsync(Windows::Storage::FileAccessMode::Read);

        op->Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler<Windows::Storage::Streams::IRandomAccessStream^>(
                [this, readCompletedFunc](Windows::Foundation::IAsyncOperation<Windows::Storage::Streams::IRandomAccessStream^>^ operation, AsyncStatus status)
                {
                    bool success = true;
                    try
                    {
                        this->onAccessReady(operation->GetResults(), readCompletedFunc);
                    }
                    catch(Platform::Exception^ e)
                    {
                        success = false;
                    }
                    if (success == false)
                    {
                        // GetResults() threw an exception.
                        readCompletedFunc(success, nullptr);
                    }
                }
            );
    }

private:

    void onAccessReady(Windows::Storage::Streams::IRandomAccessStream^ stream, std::function<void(bool, const std::shared_ptr<imemory_stream>& ms)> readCompletedFunc)
    {
        std::shared_ptr<Windows::Storage::Streams::DataReader^> dataReader = std::make_shared<Windows::Storage::Streams::DataReader^>(nullptr);
        *dataReader = ref new Windows::Storage::Streams::DataReader(stream);

        Windows::Storage::Streams::DataReaderLoadOperation^ op = (*dataReader)->LoadAsync(static_cast<unsigned int>(stream->Size));

        op->Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler<unsigned int>(
                [this, dataReader, readCompletedFunc](Windows::Foundation::IAsyncOperation<unsigned int>^ operation, AsyncStatus status)
                {
                    bool success = true;
                    std::shared_ptr<istream> ms;

                    try
                    {
                        unsigned int size = operation->GetResults();

                        Platform::Array<unsigned char>^ buffer = ref new Platform::Array<unsigned char>(static_cast<uint32>(size));
                        uint32 bytesRead = static_cast<uint32>(size);

                        (*dataReader)->ReadBytes(buffer);

                        ms = createMemoryStream((void*)nullptr, 0);

                        // now copy it to the memory stream
                        size_t bytesWritten = 0;
                        bytesWritten = ms->write(buffer->begin(), bytesRead);
                    }
                    catch(Platform::Exception^ e)
                    {
                        success = false;
                    }
                    readCompletedFunc(success, std::dynamic_pointer_cast<imemory_stream>(ms));
                }
            );
    }

#if 0
    void createMemoryStreamFromFileAsync_Legacy(std::function<void(const std::shared_ptr<imemory_stream>& ms)> readCompletedFunc)
    {
        randomAccessTask = Concurrency::task<Windows::Storage::Streams::IRandomAccessStream^>(_storageFile->OpenAsync(Windows::Storage::FileAccessMode::Read));
        auto t = randomAccessTask.then([context](Windows::Storage::Streams::IRandomAccessStream^ stream) mutable -> Windows::Foundation::IAsyncOperation<unsigned int>^ 
        {
            *context = ref new Windows::Storage::Streams::DataReader(stream); // use GetInputStreamAt(0)) to implement Seek (doesn't work in the current build of windows)

            return (*context)->LoadAsync(static_cast<unsigned int>(stream->Size));

        }).then([context](Concurrency::task<unsigned int> t) -> std::shared_ptr<imemory_stream>
        {
            unsigned int size = t.get();

            Platform::Array<unsigned char>^ buffer = ref new Platform::Array<unsigned char>(static_cast<uint32>(size));
            uint32 bytesRead = static_cast<uint32>(size);

            (*context)->ReadBytes(buffer);

            std::shared_ptr<istream> ms = createMemoryStream((void*)nullptr, 0);

            // now copy it to the memory stream
            size_t bytesWritten = 0;
            bytesWritten = ms->write(buffer->begin(), bytesRead);

            return std::dynamic_pointer_cast<imemory_stream>(ms);
        });
        return t;
    }
#endif

    void initializePropertiesAsync(std::function<void(bool)> initializeCompleted)
    {
        std::shared_ptr<storage_file> p_this = std::static_pointer_cast<storage_file>(shared_from_this());

        Windows::Foundation::IAsyncOperation<Windows::Storage::FileProperties::BasicProperties^>^ op =  p_this->_storageFile->GetBasicPropertiesAsync();
        op->Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler<Windows::Storage::FileProperties::BasicProperties^>(
                [p_this, initializeCompleted](Windows::Foundation::IAsyncOperation<Windows::Storage::FileProperties::BasicProperties^>^ operation, AsyncStatus status)
                {
                    bool success = true;
                    try
                    {
                        p_this->_storageFileProperties = operation->GetResults();
                    }
                    catch(Platform::Exception^ e)
                    {
                        success = false;
                    }
                    initializeCompleted(success);
                }
            );
    }


    Windows::Storage::StorageFile^ _storageFile;
    Windows::Storage::FileProperties::BasicProperties^ _storageFileProperties;
};

inline std::shared_ptr<istorage_file> createStorageFile()
{
    return storage_file::create();
}

inline std::shared_ptr<istorage_file> createStorageFile(Windows::Storage::StorageFile^ storageFile)
{
    return storage_file::create(storageFile);
}

}}// baja::storage